using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using PokerLearner;
using System.IO;
using System.Threading;
using System.Net;

namespace PokerLearner.Poker
{
	public class PokerClient : PokerManager
	{
		#region Singleton Instance

		protected static PokerClient instance;

		public static PokerClient Instance
		{
			get
			{
				if (instance == null)
				{
					instance = new PokerClient();
				}
				return instance;
			}
		}

		#endregion

		#region Private Fields

		private Socket socket;
		private Thread serverThread;
		
		#endregion

		#region Player

		private Player player;

		public Player Player
		{
			get { return player; }
		}

		/// <summary>
		/// Used to add several player to the base manager at once
		/// </summary>
		/// <param name="players"></param>
		public void AddPlayers(List<Player> players)
		{
			foreach (Player player in players)
			{
				AddPlayer(player);
			}
		}

		#endregion

		#region Pocket & Hand

		private Card[] pocket;
		public Card[] Pocket
		{
			get { return pocket; }
		}

		private void SetPocket(Card[] cards)
		{
			if (cards[0].Count < cards[0].Count)
			{
				pocket[0] = cards[0];
				pocket[1] = cards[1];
			}
			else
			{
				pocket[0] = cards[1];
				pocket[1] = cards[0];
			}
		}

		private Hand hand;
		public Hand Hand
		{
			get { return hand; }
		}

		#endregion

		#region Play Control		

		private void RequestPlay(List<Play> availablePlays)
		{
			// Automaticly send the blinds in return
			if (availablePlays.Count == 1 && (availablePlays[0].Type == PlayType.SmallBlind || availablePlays[0].Type == PlayType.BigBlind || availablePlays[0].Type == PlayType.Check))
			{
				SendPacket(PacketType.Play, availablePlays[0]);
			}
			else if (PlayRequested != null)
			{
				PlayRequested(this, new PlayListEventArgs(availablePlays));
			}
		}

		public void MakePlay(Play play)
		{
			SendPacket(PacketType.Play, play);
		}

		#endregion

		#region Round Control

		public void StartRound(Round round)
		{
			hand = null;

			Round = round;

			pocket = new Card[2];
									

			Round.NewPlay += new EventHandler<PlayEventArgs>(Round_NewPlay);
		}

		#endregion

		#region Client Control Methods

		public void Connect(string server, string name, int stack)
		{
			player = new Player(name, stack);
			player.Stack = stack;

			IPHostEntry hostEntry = Dns.GetHostEntry(server);
			
			foreach (IPAddress ip in hostEntry.AddressList)
			{
				if (ip.AddressFamily == AddressFamily.InterNetwork)
				{
					socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
					socket.Connect(ip, PokerServer.DefaultPort);
					if (socket.Connected)
						break;
				}
			}
			
			SendPacket(PacketType.Connect, Player);

			// Start the server listener
			serverThread = new Thread(new ThreadStart(ServerStreamListenerThread));
			serverThread.Start();			

			if (Connected != null)
			{
				Connected(this, null);
			}
		}

		public void Disconnect()
		{
			RemoveAllPlayers();

			SendPacket(PacketType.Disconnect, Player);

			if (Disconnected != null)
				Disconnected(this, null);
			
			// Stop the server listener
			serverThread.Abort();			
		}

		#endregion

		#region Send/Recieve Packet

		protected void SendPacket(PacketType type, object payload)
		{
			Packet packet = new Packet(type, payload);

			byte[] buffer = new byte[6144];
			MemoryStream memStream = new MemoryStream(buffer, 0, buffer.Length);

			BinaryFormatter formatter = new BinaryFormatter();
			formatter.Serialize(memStream, packet);

			socket.Send(buffer);
			//System.Diagnostics.Debug.WriteLine(String.Format("Client Thread: {0} Packet: {1} sent to {2}", Thread.CurrentThread.ManagedThreadId, packet, socket.RemoteEndPoint));
		}

		protected Packet RecievePacket()
		{
			byte[] buffer = new byte[6144];
			MemoryStream memStream = new MemoryStream(buffer, 0, buffer.Length);
			BinaryFormatter formatter = new BinaryFormatter();
			
			Packet ret = null;
			int num = 0;
			do
			{
				num += socket.Receive(buffer, num, buffer.Length - num, SocketFlags.None);
				//System.Diagnostics.Debug.WriteLine(String.Format("Client Thread: {0} Read {1} bytes from {2}", Thread.CurrentThread.ManagedThreadId, num, socket.RemoteEndPoint)); 
			}
			while (num < buffer.Length);
			
			ret = (Packet)formatter.Deserialize(memStream);
			
			//System.Diagnostics.Debug.WriteLine(String.Format("Client Thread: {0} Packet: {1} recieved from {2}", Thread.CurrentThread.ManagedThreadId, ret, socket.RemoteEndPoint));
			
			return ret;
		}

		#endregion

		#region Events

		public event EventHandler Connected;
		public event EventHandler Disconnected;
		public event EventHandler<PlayListEventArgs> PlayRequested;
		public event EventHandler CardUpdate;
		public event EventHandler<EndRoundEventArgs> RoundEnded;
		
		#endregion

		#region Constructor

		protected PokerClient()
		{
			pocket = new Card[2];
		}

		#endregion

		#region Round Event Handlers

		void Round_NewPlay(object sender, PlayEventArgs args)
		{
			if (args.Play.Type != PlayType.Check && args.Play.Type != PlayType.Fold)
			{
				// Get the local instance of the player and update the stack. This is done to ensure the calls to 
				// the proper event handlers.
				Players[Players.IndexOf(args.Play.Player)].Stack -= args.Play.Value;
			}
		}

		#endregion

		#region Server Stream Listener Thread

		private void ServerStreamListenerThread()
		{			
			while (true)
			{
				Packet packet = RecievePacket();

				if (packet != null)
				{
					if (packet.Type == PacketType.Kick)
						Disconnect();
					if (packet.Type == PacketType.ConnectResponse)
						AddPlayers((List<Player>)packet.Payload);
					else if (packet.Type == PacketType.Connect)
						AddPlayer((Player)packet.Payload);
					else if (packet.Type == PacketType.Disconnect)
						RemovePlayer((Player)packet.Payload);
					else if (packet.Type == PacketType.NewRound)
						StartRound((Round)packet.Payload);
					else if (packet.Type == PacketType.PlayRequest)
						RequestPlay((List<Play>)packet.Payload);
					else if (packet.Type == PacketType.Play)
						Round.AddPlay((Play)packet.Payload);
					else if (packet.Type == PacketType.Pocket)
					{
						Card[] pocket = (Card[])packet.Payload;
						SetPocket(pocket);

						hand = Hand.CreateBestHand(pocket, Round.CommonCards);

						if (CardUpdate != null)
							CardUpdate(this, null);
					}
					else if (packet.Type == PacketType.Flop)
					{
						Card[] flop = (Card[])packet.Payload;
						Round.SetFlop(flop);

						hand = Hand.CreateBestHand(pocket, Round.CommonCards);

						if (CardUpdate != null)
							CardUpdate(this, null);
					}
					else if (packet.Type == PacketType.Turn)
					{
						Round.SetTurn((Card)packet.Payload);

						hand = Hand.CreateBestHand(pocket, Round.CommonCards);

						if (CardUpdate != null)
							CardUpdate(this, null);
					}
					else if (packet.Type == PacketType.River)
					{
						Round.SetRiver((Card)packet.Payload);

						hand = Hand.CreateBestHand(pocket, Round.CommonCards);

						if (CardUpdate != null)
							CardUpdate(this, null);
					}
					else if (packet.Type == PacketType.EndRound)
					{
						EndRoundEventArgs payload = (EndRoundEventArgs)packet.Payload;
						foreach (Player player in payload.Winners)
						{
							Players[Players.IndexOf((player))].Stack += payload.PlayerPayoff[player];
						}

						if (RoundEnded != null)
							RoundEnded(this, payload);
					}
				}

				Thread.Sleep(0);
			}
		}				

		#endregion
	}
}
